﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace PrototypeZGame
{
    public class FuzzyHumanAI : AIController
    {
        private enum State
        {
            Sleeping,
            Resting,
            Searching,
            Running,
            Barricading,
            Fighting,
            Dead,
        }

        private enum TargetType
        {
            Food,
            Weapon,
            Field,
            Zombie,
        }

        TargetType targetType;
        State state;
        public Human human;
        EnemyStrength enemyStrength;
        float humanStrength;
        LinguisticVariables fuzEnemyStrength, fuzHumanStrength;
        FuzzySetName danger;
        List<int> AICode_EnemyStrength;
        List<int> AICode_HumanStrength;
        int fleeing = 0;

        public int[] mDNA;

        public Int64 birthTime, deathTime = -10;

        public Int64 age
        {
            get
            {
                if (deathTime >= 0)
                {
                    return deathTime - birthTime;
                }
                else
                {
                    return world.time - birthTime;
                }
            }
        }


        public FuzzyHumanAI(World world, int [] DNA)
            : base(world)
        {
            state = State.Searching;
            birthTime = world.time;
            Debug.Assert(DNA.Length == 7);
            mDNA = DNA;

            AICode_EnemyStrength = new List<int>();
            AICode_EnemyStrength.Add(DNA[0]);
            AICode_EnemyStrength.Add(DNA[0] + DNA[1]);
            AICode_EnemyStrength.Add(DNA[0] + DNA[1] + DNA[2]);
            AICode_EnemyStrength.Add(DNA[0] + DNA[1] + DNA[2] + DNA[3]);


            AICode_HumanStrength = new List<int>();
            AICode_HumanStrength.Add(DNA[4]);
            AICode_HumanStrength.Add(DNA[4] + DNA[5]);
            AICode_HumanStrength.Add(DNA[4] + DNA[5] + DNA[6]);


            fuzEnemyStrength = new LinguisticVariables();
            fuzEnemyStrength.Add(new FuzzySetL(FuzzySetName.Strength_None, AICode_EnemyStrength[0], AICode_EnemyStrength[1]));
            fuzEnemyStrength.Add(new FuzzySetA(FuzzySetName.Strength_Weak, AICode_EnemyStrength[0], AICode_EnemyStrength[1], AICode_EnemyStrength[2]));
            fuzEnemyStrength.Add(new FuzzySetA(FuzzySetName.Strength_Medium, AICode_EnemyStrength[1], AICode_EnemyStrength[2], AICode_EnemyStrength[3]));
            fuzEnemyStrength.Add(new FuzzySetF(FuzzySetName.Strength_Strong, AICode_EnemyStrength[2], AICode_EnemyStrength[3]));

            fuzHumanStrength = new LinguisticVariables();
            fuzHumanStrength.Add(new FuzzySetL(FuzzySetName.Strength_Weak, AICode_HumanStrength[0], AICode_HumanStrength[1]));
            fuzHumanStrength.Add(new FuzzySetA(FuzzySetName.Strength_Medium, AICode_HumanStrength[0], AICode_HumanStrength[1], AICode_HumanStrength[2]));
            fuzHumanStrength.Add(new FuzzySetF(FuzzySetName.Strength_Strong, AICode_HumanStrength[1], AICode_HumanStrength[2]));            
        }
       

        private Actor findResources()
        {            
            Weapon retWeapon = findNewWeapon();
            Weapon currentWeapon = unit.inventory.rightHand as Weapon;

            if (retWeapon != null && (currentWeapon == null || currentWeapon.damage / currentWeapon.EC < retWeapon.damage / retWeapon.EC))
            {
                return retWeapon;
            }

            if (unit.inventory.Count < unit.inventory.capacity - 1)
            {
                Food ret = findFood();
                if (ret != null)
                    return ret;
            }

            return findNewPlace();
        }

        private Food findFood()
        {
            List<Food> food = human.findFood();
            if (food != null && food.Count > 0)
            {
                Food best = food[0];
                foreach (Food f in food)
                {
                    if (World.Distance(f, unit) < World.Distance(best, unit))
                    {
                        best = f;
                    }
                }
                targetType = TargetType.Food;
                return best;
            }
            else
            {
                return null;
            }
        }

        private Weapon findNewWeapon()
        {
            List<Weapon> weapons = human.findWeapon();
            if (weapons != null && weapons.Count > 0)
            {
                float max = 0, w;
                Weapon best = weapons[0];
                foreach (Weapon weapon in weapons)
                {
                    w = weapon.damage / weapon.EC;
                    if (w > max)
                    {
                        max = w;
                        best = weapon;
                    }
                    else if (w == max)
                    {
                        if (World.Distance(weapon, unit) < World.Distance(best, unit))
                        {
                            best = weapon;
                        }
                    }
                }
                targetType = TargetType.Weapon;
                return best;
            }
            else
            {
                return null;
            }
        }

        private Field findNewPlace()
        {
            Vec2 v;
            bool c = false;

            int range = (int)(unit.viewDistance);

            do
            {
                v = unit.position + new Vec2(world.rand.Next(2 * range) - range, world.rand.Next(2 * range) - range);
                c = terrain.isPositionValid(v);
            } while (!c);

            targetType = TargetType.Field;
            return terrain.field[v.x, v.y];
        }

        

        private Zombie FindWeekestZombieInRange()
        {
            Vec2 a = unit.position - new Vec2(1);
            Vec2 b = unit.position + new Vec2(1);
            Zombie ret = null;
            for (Vec2 v = a; v.x <= b.x; v.x++)
            {
                for (v.y = a.y; v.y <= b.y; v.y++)
                {
                    if (terrain.isPositionValid(v))
                    {
                        foreach (Zombie zombie in terrain.getField(v).onFieldZombies)
                        {

                            // TODO: poprawić warunki
                            if (!zombie.dead && ret == null)
                            {
                                ret = zombie;
                            }
                            else if (!zombie.dead && zombie.HP > ret.HP)
                            {
                                ret = zombie;
                            }
                        }
                    }
                }
            }

            return ret;
        }

        private bool eat()
        {
            int i = 0;
            for (; i < unit.inventory.Count; i++)
            {
                Food food = unit.inventory.at(i) as Food;
                if (food != null)
                {
                    human.useItem(i);
                    return true;
                }
            }
            return false;
        }

        private void chooseBestWeapon()
        {
            int bestIndex = -1;
            float max = 0, w;
            for (int i = 0; i < unit.inventory.Count; i++)
            {
                Weapon weapon = unit.inventory.at(i) as Weapon;
                if (weapon != null)
                {
                    w = weapon.damage / weapon.EC;
                    if (w > max)
                    {
                        max = w;
                        bestIndex = i;
                    }
                }
            }

            if (bestIndex == -1)
            {
                unit.inventory.rightHand = null;
            }
            else
            {
                unit.useItem(bestIndex);
            }
        }

        private void attackZombie()
        {
            Vec2 targetPosition = unit.position + World.DirToVec2(path[0]);
            Field targetField = terrain.getField(targetPosition);
            List<Zombie> zombies = targetField.onFieldZombies;
            foreach (Zombie z in zombies)
            {
                if (!z.dead)
                {
                    unit.Attack(z);
                }
            }

        }

        private void openDoor()
        {
            Vec2 targetPosition = unit.position + World.DirToVec2(path[0]);
            Field targetField = terrain.getField(targetPosition);
            List<Item> items = targetField.onFieldItems;
            foreach (Item item in items)
            {
                Door door = item as Door;
                if (door != null)
                {
                    unit.open(door);
                    break;
                }
            }
        }

        public override void UnitDied()
        {
            deathTime = world.time;
            state = State.Dead;
        }





        public override void Posses(Unit _unit)
        {
            base.Posses(_unit);
            Human _human = _unit as Human;
            if (_human != null)
            {
                human = _human;
            }
        }

        public override void UnPosses()
        {
            base.UnPosses();
            human = null;
        }

        public override string ToString()
        {
            string ret = base.ToString();
            ret += enemyStrength + "\n"
                + danger + "\n";
            return ret;
        }

        public FuzzySetName evaluateDanger()
        {
            enemyStrength = human.evaluateEnemiesStrength();
            humanStrength = human.evaluateHumanStrength();

            // Fuzyfikacja
            List<FuzzyValue> lEnemyStrength = fuzEnemyStrength.getValue(enemyStrength.s);
            List<FuzzyValue> lHumanStrength = fuzHumanStrength.getValue(humanStrength);

            // Wnioskowanie
            List<FuzzyValue> lDanger = new List<FuzzyValue>();
            lDanger.Add(new FuzzyValue(FuzzySetName.Danger_None, 0));
            lDanger.Add(new FuzzyValue(FuzzySetName.Danger_Small, 0));
            lDanger.Add(new FuzzyValue(FuzzySetName.Danger_Medium, 0));
            lDanger.Add(new FuzzyValue(FuzzySetName.Danger_Big, 0));

            for (int e = 0; e < lEnemyStrength.Count; e++)
            {
                for (int h = 0; h < lHumanStrength.Count; h++)
                {
                    int n = -1;
                    if (lEnemyStrength[e].name == FuzzySetName.Strength_None)
                    {
                        n = 0;
                    }
                    else if ((lEnemyStrength[e].name != FuzzySetName.Strength_Weak && lHumanStrength[h].name == FuzzySetName.Strength_Weak)
                      || (lEnemyStrength[e].name == FuzzySetName.Strength_Strong && lHumanStrength[h].name == FuzzySetName.Strength_Medium))
                    {
                        n = 3;
                    }
                    else if ((lEnemyStrength[e].name == FuzzySetName.Strength_Weak && lHumanStrength[h].name == FuzzySetName.Strength_Medium)
                      || (lEnemyStrength[e].name == FuzzySetName.Strength_Medium && lHumanStrength[h].name == FuzzySetName.Strength_Strong)
                      || (lEnemyStrength[e].name == FuzzySetName.Strength_Weak && lHumanStrength[h].name == FuzzySetName.Strength_Strong))
                    {
                        n = 1;
                    }
                    else if ((lEnemyStrength[e].name == FuzzySetName.Strength_Weak && lHumanStrength[h].name == FuzzySetName.Strength_Weak)
                            || (lEnemyStrength[e].name == FuzzySetName.Strength_Medium && lHumanStrength[h].name == FuzzySetName.Strength_Medium)
                            || (lEnemyStrength[e].name == FuzzySetName.Strength_Strong && lHumanStrength[h].name == FuzzySetName.Strength_Strong))
                    {
                        n = 2;
                    }

                    lDanger[n].value += Math.Min(lEnemyStrength[e].value, lHumanStrength[h].value);

                }
            }

            // Defuzyfikacja metodą środkowego maksimum
            double max = lDanger[0].value;
            int idm = 0;
            for (int i = 1;i<lDanger.Count ;i++ )
            {
                if (lDanger[i].value >= max)
                {
                    max = lDanger[i].value;
                    idm = i;
                }
            }

            return lDanger[idm].name;
        }

        public void gatherResources()
        {
            if (AttackAdjactentZombie()) return;

            if (target == null)
            {
                target = findResources();
            }

            if (World.Distance(unit, target) <= 0.5)
            {
                if (targetType == TargetType.Weapon)
                {
                    unit.takeItem((InventoryItem)target);
                    chooseBestWeapon();
                }
                else if (targetType == TargetType.Food)
                {
                    unit.takeItem((InventoryItem)target);
                }
                target = null;
            }
            else
            {
                if (human.maxHP - human.HP > 4)
                {
                    if (eat())
                    {
                        return;
                    }
                }

                if (fleeing > 0)
                {
                    unit.running = true;
                }
                else
                {
                    unit.running = false;
                }

                Result result = MoveToTarget((int)unit.viewDistance + 2);
                if (result == Result.BlockingDoor)
                {
                    openDoor();
                    return;
                }
                else if (result == Result.BlockingItem)
                {
                    target = null;
                    return;
                }
                else if (result == Result.BlockingZombie)
                {
                    attackZombie();
                    return;
                }
                else if (result == Result.BlockingHuman)
                {
                    swapHuman();
                }
                else if (result == Result.NoPath)
                {
                    target = null;
                    return;
                }

                if (fleeing > 0)
                {
                    result = MoveToTarget((int)unit.viewDistance + 2);
                    if (result == Result.BlockingItem)
                    {
                        target = null;
                        return;
                    }
                    else if (result == Result.BlockingHuman)
                    {
                        swapHuman();
                    }
                    else if (result == Result.NoPath)
                    {
                        target = null;
                        return;
                    }
                }
            }


        }

        private void swapHuman()
        {
            if (unit.swapCount <= 0)
            {
                Vec2 targetPosition = unit.position + World.DirToVec2(path[0]);
                Field targetField = terrain.getField(targetPosition);
                List<Human> humans = targetField.onFieldHumans;

                for (int i = 0; i < humans.Count; i++)
                {
                    if (!humans[i].dead)
                    {
                        unit.swap(humans[i]);
                    }
                }
            }

        }


        public void fightOrGather()
        {
            if (AttackAdjactentZombie()) return;

            if (target == null)
            {
                Weapon retWeapon = findNewWeapon();
                Weapon currentWeapon = unit.inventory.rightHand as Weapon;

                if (retWeapon != null && (currentWeapon == null || currentWeapon.damage / currentWeapon.EC < retWeapon.damage / retWeapon.EC))
                {
                    target = retWeapon;
                }
                else
                {
                    target = findNewPlace();
                }

            }

            if (World.Distance(unit, target) <= 0.5)
            {
                if (targetType == TargetType.Weapon)
                {
                    unit.takeItem((InventoryItem)target);
                    chooseBestWeapon();
                }
                else if (targetType == TargetType.Food)
                {
                    unit.takeItem((InventoryItem)target);
                }
                target = null;
            }
            else
            {
                if (human.maxHP - human.HP > 4)
                {
                    if (eat())
                    {
                        return;
                    }
                }

                if (fleeing > 0)
                {
                    unit.running = true;
                }
                else
                {
                    unit.running = false;
                }

                Result result = MoveToTarget((int)unit.viewDistance + 2);
                if (result == Result.BlockingDoor)
                {
                    openDoor();
                    return;
                }
                else if (result == Result.BlockingItem)
                {
                    target = null;
                    return;
                }
                else if (result == Result.BlockingZombie)
                {
                    attackZombie();
                    return;
                }
                else if (result == Result.BlockingHuman)
                {
                    swapHuman();
                }
                else if (result == Result.NoPath)
                {
                    target = null;
                    return;
                }

                if (fleeing > 0)
                {
                    result = MoveToTarget((int)unit.viewDistance + 2);
                    if (result == Result.BlockingItem)
                    {
                        target = null;
                        return;
                    }
                    else if (result == Result.BlockingHuman)
                    {
                        swapHuman();
                    }
                    else if (result == Result.NoPath)
                    {
                        target = null;
                        return;
                    }
                }
            }
        }
        private bool AttackAdjactentZombie()
        {
            if (unit.inventory.rightHand != null)
            {
                Zombie retZ = FindWeekestZombieInRange();
                if (retZ != null)
                {
                    unit.Attack(retZ);
                    return true;
                }
            }
            return false;
        }

        public override void newTurn()
        {
            if (state == State.Dead)
            {
                return;
            }

            unit.newTurn();
            if (fleeing > 0) fleeing--;

            danger = evaluateDanger();

            switch(danger){
                case FuzzySetName.Danger_None:
                    gatherResources();
                    break;
                case FuzzySetName.Danger_Small:
                    gatherResources();
                    break;
                case FuzzySetName.Danger_Medium:
                    fightOrGather();
                    break;
                case FuzzySetName.Danger_Big:
                    Flee();
                    break;
                default:
                    break;
            }
           
        }

        private Result Flee()
        {
            if (fleeing == 0)
            {
                fleeing = 13;
            }
            int searchDistance = (int)unit.viewDistance + 2;
            Vec2 S = unit.position;
            Vec2 A = new Vec2(S.x - searchDistance, S.y - searchDistance);
            Vec2 B = new Vec2(S.x + searchDistance + 1, S.y + searchDistance + 1);
            Vec2 D = unit.position - enemyStrength.v;

            path = Dijskra.getPath(terrain, S, D, A, B);

            if (path == null)
            {
                target = findNewPlace();
                D = target.position;

                path = Dijskra.getPath(terrain, S, D, A, B);

                if (path == null)
                {
                    target = null;
                    return Result.NoPath;
                }

            }

            bool oldRunning = unit.running;
            unit.running = true;
            Result result = walk(path[0]);
            if (result == Result.BlockingDoor)
            {
                openDoor();
            }
            else if (result == Result.BlockingItem)
            {
                target = null;
            }
            else if (result == Result.BlockingZombie)
            {
                attackZombie();
            }
            else if (result == Result.NoBlock)
            {
                if (path.Count >= 2) walk(path[1]);
            }

            unit.running = oldRunning;

            return Result.Success;
        }
    }
}
